/**
 * 
 */
package com.I9lou.se.lurker.remote.support;

import static java.util.concurrent.TimeUnit.SECONDS;

import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;

import org.apache.lucene.document.Document;
import org.apache.lucene.facet.taxonomy.CategoryPath;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.Similarity;
import org.apache.lucene.search.TopDocs;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.I9lou.se.lurker.facet.search.support.SimpleFacetSearchService;
import com.I9lou.se.lurker.facet.search.support.SimpleMultiFacetService;
import com.I9lou.se.lurker.index.IndexOptimizer;
import com.I9lou.se.lurker.remote.IndexSearcherService;
import com.I9lou.se.lurker.remote.SearchCommand;
import com.I9lou.se.lurker.remote.SearchResult;
import com.I9lou.se.lurker.search.IndexSearcherFactory;

/**
 * 提供索引查询服务，在指定时间周期内将会重新打开该服务所维护的IndexSearcher实例，
 * 使之前的索引更新可见。
 * @author chenwei
 * @date 2011-04-08
 * @ThreadSafe
 */
public class AutoReopenIndexSearcherService implements IndexSearcherService {
	
	private static final Logger LOG = LoggerFactory.getLogger(AutoReopenIndexSearcherService.class);
	
	/**
	 * 索引文件夹名称列表缓存池，记录了不同搜索应用所使用的索引文件夹名称列表
	 * Key->搜索应用ID
	 * Value->隶属于该应用的索引文件夹名称列表
	 */
	private ConcurrentHashMap<Integer, String[]> indexDirNamesPool = new ConcurrentHashMap<Integer, String[]>();
	
	/**
	 * 预热搜索命令缓存池，不同搜索应用的IndexSearcher实例将会使用池中的搜索命令进行预热操作
	 * Key->搜索应用ID
	 * Value->预热搜索命令列表
	 */
	private ConcurrentHashMap<Integer, List<SearchCommand>> warmCommandPool = new ConcurrentHashMap<Integer, List<SearchCommand>>();
	
	/**
	 * 上一次被引用的IndexSearcher缓存池
	 * Key->搜索应用ID
	 * Value->该搜索应用对应的IndexSearcher实例
	 */
	private ConcurrentHashMap<Integer, IndexSearcher> lastSearcherPool = new ConcurrentHashMap<Integer, IndexSearcher>();
	
	/**
	 * 当前正在被引用的IndexSearcher缓存池
	 * Key->搜索应用ID
	 * Value->该搜索应用对应的IndexSearcher实例
	 */
	private ConcurrentHashMap<Integer, IndexSearcher> currentSearcherPool = new ConcurrentHashMap<Integer, IndexSearcher>();
	
	private static final Document[] EMPTY_DOCUMENT_ARRAY = new Document[0];
	
	/**
	 * 没有进行实际索引搜索而返回的结果，通常为该应用的IndexSearcher没有进入服务状态
	 */
	private static final SearchResult IGNORE_SEARCH_RESULT = new SearchResult().setIgnore(true).setDocs(EMPTY_DOCUMENT_ARRAY);
	
	private ScheduledExecutorService scheduler = null;
	
	/**
	 * IndexSearcher交替更新的时间周期（单位：秒），
	 * 该值不能小于60（1分钟）的值
	 * @param refreshInteval
	 */
	private int refreshInteval = 3 * 60;
	
	/**
	 * IndexSearcher实例工厂
	 */
	private IndexSearcherFactory indexSearcherFactory;
	
	/**
	 * 索引优化器，用于查询指定搜索应用是否正在执行优化
	 */
	private IndexOptimizer indexOptimizer;
	
	private Similarity similarity = null;
	
//	private ConcurrentHashMap<String, CategoryPath> categoryPathPool = new ConcurrentHashMap<String, CategoryPath>();
	
	private SimpleFacetSearchService facetSearchService;
	
	private SimpleMultiFacetService multiFacetService;
	
	public void setFacetSearchService(SimpleFacetSearchService facetSearchService) {
		this.facetSearchService = facetSearchService;
	}

	public void setMultiFacetService(SimpleMultiFacetService multiFacetService) {
		this.multiFacetService = multiFacetService;
	}

	/**
	 * 设置IndexSearcher交替更新的时间周期（单位：秒），
	 * 不能设置小于60（1分钟）的值。
	 * 默认值为3分钟。
	 * @param refreshInteval
	 */
	public void setRefreshInteval(int refreshInteval) {
		if( refreshInteval >= 60 ) //不能设置小于1分钟的值
			this.refreshInteval = refreshInteval;
	}

	/**
	 * 设置IndexSearcher实例工厂
	 * @param indexSearcherFactory
	 */
	public void setIndexSearcherFactory(IndexSearcherFactory indexSearcherFactory) {
		this.indexSearcherFactory = indexSearcherFactory;
	}
	
	/**
	 * 设置索引优化器，用于查询指定搜索应用是否正在执行优化
	 * @param indexOptimizer
	 */
	public void setIndexOptimizer(IndexOptimizer indexOptimizer) {
		this.indexOptimizer = indexOptimizer;
	}

	public void setSimilarity(Similarity similarity) {
		this.similarity = similarity;
	}

	/**
	 * 初始化函数，在服务启动时应调用此方法进行初始化操作。
	 */
	public void init() {
		long initialDelay = 60; //第一次执行reopen操作在3分钟后
		long startTime = System.currentTimeMillis();
		scheduler = Executors.newSingleThreadScheduledExecutor();
		scheduler.scheduleWithFixedDelay(new RefreshSearcherTask(), initialDelay, refreshInteval, SECONDS);
		LOG.info( "AutoReopenIndexSearcherService init finished. initialDelay: {}s. delay: {}s. cost: {}ms",
				  new Object[]{initialDelay, refreshInteval, System.currentTimeMillis()-startTime} );
	}
	
	/**
	 * 销毁函数，在服务关闭时调用此方法进行资源释放。
	 */
	public void destroy() {
		scheduler.shutdown();
		destroyPool(lastSearcherPool);
		destroyPool(currentSearcherPool);
	}
	
	/**
	 * 销毁指定缓存池中的所有IndexSearcher实例
	 * 
	 * @param readerPool
	 */
	private void destroyPool(ConcurrentHashMap<Integer, IndexSearcher> searcherPool) {
		for (Integer appId : searcherPool.keySet()) {
			try {
				long startTime = System.currentTimeMillis();
				IndexSearcher searcher = searcherPool.get(appId);
				if( searcher != null )
					searcher.close();
				LOG.info("***Close Searcher[{}] success. cost: {}ms", appId,
						 System.currentTimeMillis() - startTime);
			} catch (Exception e) {
				LOG.error("Unrecoverable error! Can't close Searcher: " + appId, e);
			}
		}
	}
	
	@Override
	public void setupIndexSearcher(int appId, String[] indexDirNames, 
					 			  List<SearchCommand> warmCommandList) {
		if( indexDirNamesPool.get(appId) == null ) {
			indexDirNamesPool.put(appId, indexDirNames);
			LOG.info("Setup IndexSearcher success. appId: {}; Directories: {}", appId,
					 Arrays.toString(indexDirNames));
		}
		if( warmCommandList!=null && warmCommandPool.get(appId)==null )
			warmCommandPool.put(appId, warmCommandList);
	}

	@Override
	public SearchResult search(int appId, SearchCommand command) {
		try {
			IndexSearcher searcher = currentSearcherPool.get(appId);
			if( searcher != null ) {
				searcher.setDefaultFieldSortScoring(true, false);
				searcher.setSimilarity(similarity);
				return doSearch(searcher, command);
			} else {
				return IGNORE_SEARCH_RESULT;
			}
		} catch (Exception e) {
			throw new IllegalStateException("appId: "  + appId 
											+ ", command: " + command, e);
		}
	}
	
	@Override
	public List<Map<String, Object>> count(int appId, SearchCommand command, String[] facetCategories, int facetResultLimit) {
		CategoryPath categoryPath = new CategoryPath(facetCategories);
		IndexSearcher searcher = currentSearcherPool.get(appId);
		if(searcher == null){
			LOG.error("[Search-Count]searcher is not aviliable now!appId: {}; facetCategories: {}", appId, Arrays.toString(facetCategories));
			return Collections.emptyList();
		}
		searcher.setDefaultFieldSortScoring(true, false);
		return facetSearchService.search(appId, categoryPath, facetResultLimit, command, searcher);
	}
	
	@Override
	public List<Map<String, Object>> count(int appId, SearchCommand command, String facetField) {
		try {
			IndexSearcher searcher = currentSearcherPool.get(appId);
			if( searcher != null ) {
				searcher.setDefaultFieldSortScoring(true, false);
				searcher.setSimilarity(similarity);
				return multiFacetService.search(searcher, command, facetField);
			} else {
				LOG.error("[Search-CountF]searcher is not aviliable now!appId: {}; facetField: {}", appId, facetField);
				return Collections.emptyList();
			}
		} catch (Exception e) {
			throw new IllegalStateException("appId: "  + appId 
											+ ", command: " + command, e);
		}
	}
	
	private SearchResult doSearch(IndexSearcher searcher, SearchCommand command) throws IOException {
		TopDocs topDocs;
		if (command.getSort() == null) {
			topDocs = searcher.search(command.getQuery(), command.getFilter(), command.getLimit());
		} else {
			topDocs = searcher.search(command.getQuery(), command.getFilter(), command.getLimit(), command.getSort());
		}
		ScoreDoc[] scoreDocs = topDocs.scoreDocs;
		SearchResult result = new SearchResult();
		result.setTotalHits(topDocs.totalHits);
		if( scoreDocs.length > command.getStartIndex() ) {
            int size = scoreDocs.length-command.getStartIndex();
			Document[] docs = new Document[size];
            float[] scores = new float[size];
			int docsIndex = 0;
			for(int i=command.getStartIndex(); i<scoreDocs.length; i++) {
				try {
					docs[docsIndex] = searcher.doc(scoreDocs[i].doc);
					scores[docsIndex] = scoreDocs[i].score;

					//LOG.info("__doc_key: {}, score: {}, explian: {}",new Object[]{docs[docsIndex].get("__doc_key"),scoreDocs[i].score,searcher.explain(command.getQuery(), scoreDocs[i].doc)});
					docsIndex++;
				}catch (Exception e){
					LOG.error("[SearchResult doSearch]"+e.getMessage());
				}
			}
			result.setDocs(docs);
            result.setScores(scores);
		} else {
			result.setDocs(EMPTY_DOCUMENT_ARRAY);
		}
		return result;
	}
	
	private class RefreshSearcherTask implements Runnable {

		@Override
		public void run() {
			try {
				long startTime = System.currentTimeMillis();
				LOG.info( "<RefreshSearcherTask>Run Searcher refresh task..." );
				refresh();
				LOG.info( "<RefreshSearcherTask>Searcher refresh task finished. cost: {}ms",
						  System.currentTimeMillis()-startTime );
			} catch (Exception e) {
				LOG.error("<RefreshSearcherTask>Refresh failed!", e);
			}
		}
		
		private void refresh() throws IOException {
			for( int appId : indexDirNamesPool.keySet() ) {
				if( !indexOptimizer.isOptimizing( appId ) ) { //如果该应用没有在执行优化操作，则进行reopen操作
					IndexSearcher newSearcher = indexSearcherFactory.create(appId, indexDirNamesPool.get(appId));
					warm(appId, newSearcher);
					IndexSearcher oldSearcher = currentSearcherPool.put(appId, newSearcher);
					if( oldSearcher != null ) {
						IndexSearcher expiredSearcher = lastSearcherPool.put(appId, oldSearcher);
						if( expiredSearcher != null )
							expiredSearcher.close();
					}
					LOG.info("Reopen Searcher[{}] success.", appId);
				} else { //如果该应用正在执行优化操作，则忽略
					LOG.info("Optimizing now. Ignore reopen Searcher[{}].", appId);
				}
			}
		}
		
		private void warm(int appId, IndexSearcher searcher) throws IOException {
			long warmStartTime = System.currentTimeMillis();
			List<SearchCommand> warmCommands = warmCommandPool.get(appId);
			if( warmCommands!=null && warmCommands.size()>0 ) {
				for(SearchCommand command : warmCommands) {
					doSearch(searcher, command);
				}
				LOG.info( "***Searcher[{}] warm cost: {}ms", appId, 
						  System.currentTimeMillis() - warmStartTime );
			}
		}
		
	}
}
